﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Acl.DataCheck
{
    /// <summary>
    /// 外键约束校验器
    /// </summary>
    internal class ConditionFKValidator : ConstraintValidator
    {
        private static readonly Dictionary<int, Tuple<string, ConditionFK>> _caches = [];

        internal class ConditionFK
        {
            /// <summary>
            /// 当列存在时才可以进行外键检查
            /// </summary>
            public bool Existed { get; set; }

            public string ColumnName { get; set; }

            public List<FK> FKS { get; } = [];
        }

        public static ConditionFK Parse(ConstraintItem constraint, StringBuilder log)
        {
            var expression = constraint.Expression;

            var key = HashCode.Combine(expression);

            if (!_caches.TryGetValue(key, out var tuple))
            {
                _caches[key] = tuple = ParseFK(constraint);
            }

            if (!string.IsNullOrEmpty(tuple.Item1))
            {
                log.AppendLine(tuple.Item1);
                return default;
            }

            return tuple.Item2;
        }

        internal class FK
        {
            public string TableName { get; set; }
            public string ColumnName { get; set; }

            public dynamic ConditionValue { get; set; }
        }

        private static Tuple<string, ConditionFK> ParseFK(ConstraintItem constraint)
        {
            var expression = constraint.Expression;
            var tokens = ParseToken(expression);
            var length = tokens.Length;

            var fks = new ConditionFK();
            var i = 0;
            var token = tokens[i++];

            fks.Existed =
                  token[0] is 'e' or 'E' &&
                  token[1] is 'x' or 'X' &&
                  token[2] is 'i' or 'I' &&
                  token[3] is 's' or 'S' &&
                  token[4] is 't' or 'T' &&
                  token[5] is 'e' or 'E' &&
                  token[6] is 'd' or 'D';
            if (fks.Existed)
            {
                token = tokens[i++];
            }

            if (!
                 (token[0] is 's' or 'S' &&
                    token[1] is 'w' or 'W' &&
                    token[2] is 'i' or 'I' &&
                    token[3] is 't' or 'T' &&
                    token[4] is 'c' or 'C' &&
                    token[5] is 'h' or 'H'))
            {
                return new Tuple<string, ConditionFK>($"ConditionFK 表达式格式错误,表达式必须要‘switch’ 或者 'existed switch' 开头: ‘{constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
            }

            fks.ColumnName = tokens[i++];

            if ((length - i) % 8 != 0)
            {
                return new Tuple<string, ConditionFK>($"ConditionFK case 表达式格式错误: ‘{constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
            }

            for (; i + 7 < length; i++)
            {
                var @case = tokens[i++];
                var caseValue = tokens[i++];
                var then = tokens[i++];
                var @ref = tokens[i++];
                var tableName = tokens[i++];
                var left = tokens[i++];
                var columnName = tokens[i++];
                var right = tokens[i];

                if (!
                 (@case[0] is 'c' or 'C' &&
                    @case[1] is 'a' or 'A' &&
                    @case[2] is 's' or 'S' &&
                    @case[3] is 'e' or 'E'))
                {
                    return new Tuple<string, ConditionFK>($"ConditionFK 表达式格式错误,: 缺失 case 关键字: {constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                }

                if (!
                (then[0] is 't' or 'T' &&
                   then[1] is 'h' or 'H' &&
                   then[2] is 'e' or 'E' &&
                   then[3] is 'n' or 'N'))
                {
                    return new Tuple<string, ConditionFK>($"ConditionFK 表达式格式错误,: 缺失 then 关键字: {constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                }

                if (!
                (@ref[0] is 'r' or 'R' &&
                   @ref[1] is 'e' or 'E' &&
                   @ref[2] is 'f' or 'F'))
                {
                    return new Tuple<string, ConditionFK>($"ConditionFK 表达式格式错误,: 缺失 ref 关键字: {constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                }

                if (left is not ("(" or "（"))
                {
                    return new Tuple<string, ConditionFK>($"ConditionFK 表达式格式错误,: 缺失 (: {constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                }

                if (right is not (")" or "）"))
                {
                    return new Tuple<string, ConditionFK>($"ConditionFK 表达式格式错误,: 缺失 ): {constraint.TableName}.{constraint.ColumnName} | ‘{constraint.Expression}’.", null);
                }

                var fk = new FK { ConditionValue = caseValue, TableName = tableName, ColumnName = columnName };
                fks.FKS.Add(fk);
            }

            return new Tuple<string, ConditionFK>(null, fks);
        }

        public override void Validate(ValidationContext ctx)
        {
            Guard.NotNull(ctx, nameof(ctx));

            if (ctx.Current.Type != ConstraintType.ConditionFK)
                throw new NotSupportedException($"{nameof(ConditionFKValidator)} 不支持 '{ctx.Current.Type}' ,出错相关信息:{ctx.Current} ");

            if (!ctx.Current.Enabled) return;

            if (!ctx.Tables.Contains(ctx.Current.TableName))
                throw new NotSupportedException($"{ctx.Current.TableName} 表名不存在 ,出错相关信息:{ctx.Current} ");

            var table = ctx.Tables[ctx.Current.TableName];
            if (!table.Members.Contains(ctx.Current.ColumnName))
                throw new NotSupportedException($"{ctx.Current.TableName}.{ctx.Current.ColumnName} 列不存在 ,出错相关信息:{ctx.Current} ");

            var columnType = table.Members[ctx.Current.ColumnName].Type;
            var defaultValue = ObjectCreator.GetDefaultValue(columnType);

            //解析表达式
            ctx.Current.ClearErrorMessage();
            var fks = Parse(ctx.Current, ctx.Logger);
            ctx.Current.Passed = fks != null;
            if (!ctx.Current.Passed) return;

            if (!table.Members.Contains(fks.ColumnName))
                throw new NotSupportedException($"{ctx.Current.TableName}.{fks.ColumnName} 列不存在 ,出错相关信息:{ctx.Current} ");

            //条件列类型
            columnType = table.Members[fks.ColumnName].Type;

            foreach (var fk in fks.FKS)
            {
                if (!ctx.Tables.Contains(fk.TableName))
                    throw new NotSupportedException($"{fk.TableName} 表名不存在 ,出错相关信息:{ctx.Current} ");

                var fkTable = ctx.Tables[fk.TableName];
                if (!fkTable.Members.Contains(fk.ColumnName))
                    throw new NotSupportedException($"{fk.TableName}.{fk.ColumnName} 列不存在 ,出错相关信息:{ctx.Current} ");

                fk.ConditionValue = Converter.Convert(fk.ConditionValue, columnType);
            }

            var notExisted = new HashSet<dynamic>();
            foreach (dynamic row in table)
            {
                var columnValue = row[ctx.Current.ColumnName];
                if (columnValue is null || Equals(columnValue, defaultValue))
                {
                    if (fks.Existed) continue;
                }

                var conditionValue = Converter.Convert(row[fks.ColumnName], columnType);

                var fk = fks.FKS.FirstOrDefault(x => x.ConditionValue == conditionValue);
                if (fk == null)
                {
                    notExisted.Add(conditionValue);
                    continue;
                }

                var range = ctx.GetFKCodes(fk.TableName, fk.ColumnName);
                if (range.Count != 0 && range[0].GetType() != columnType)
                {
                    range = range.Select(x => Converter.Convert(x, columnType)).ToList();
                }

                if (!range.Contains(columnValue))
                {
                    //
                    ctx.Current.AppendLine($"{ctx.Current.TableName}.{ctx.Current.ColumnName}={columnValue} FK值在{fk.TableName}.{fk.ColumnName} 中不存在，记录信息:{row}");
                    ctx.Current.Passed = false;
                }
            }

            if (notExisted.Count > 0)
            {
                var conditionValues = fks.FKS.Select(x => x.ConditionValue);
                ctx.Logger.AppendLine($"{ctx.Current.TableName}.{fks.ColumnName} 列中的值:({string.Join(",", notExisted)}) 在case when 条件({string.Join(",", conditionValues)})中不存在 ,出错相关信息:{ctx.Current} ");
                ctx.Current.Passed = false;
            }
        }
    }
}